package ratkit;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import org.junit.Ignore;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.statements.ExpectException;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import ratkit.RatKitTest.None;
import ratkit.parameter.RatKitParameter;
import ratkit.parameter.RatKitParameter.ParameterTypes;
import ratkit.parameter.RatKitParameter.Type;
import ratkit.parameter.RatKitParameterSweeper;
import ratkit.parameter.TestParameter;
import ratkit.parameter.exception.RatKitParameterException;
import ratkit.repast.RepastRunner;
import ratkit.repast.RepastScenarioBuilder;
import ratkit.util.RatKitConstants;
import repast.simphony.context.Context;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduleParameters;
import repast.simphony.engine.watcher.WatcheeInstrumentor;
import repast.simphony.util.collections.IndexedIterable;
import simphony.util.messages.MessageCenter;
import simphony.util.messages.MessageEvent;
import simphony.util.messages.MessageEventListener;

/**
 * This class is the {@code BlockJUnit4ClassRunner} extension for the RatKit
 * Simulation tests. RatKitRunner class analysis the JUnit4 tests and executes
 * simulation test methods according the definition that is defined using
 * {@code RatKitTest}. When a simulation test is executed {@code RatKitTest}
 * Annotation value of the current test is evaluated
 * {@code RepastScenarioBuilder}, test parameters, test iteration count is
 * calculated and required data files are prepared. At the end, test method body
 * is evaluated by a test agent in {@code RepastTestEnvironment} context and
 * simulation tests are evaluated.
 * 
 * @author İbrahim Çakırlar
 * 
 */
public class RatKitRunner extends BlockJUnit4ClassRunner {

	/**
	 * {@code RepastScenarioBuilder} class instance for the current test
	 * scenario.
	 */
	private Class<? extends RepastScenarioBuilder> scenarioBuilder = null;

	/**
	 * List of parameter types which are matched with Repast parameter
	 * converters.
	 */
	private Hashtable<String, String> parameterTypeConverters;

	/**
	 * List of parameters for the current iteration.
	 */
	private Hashtable<String, List<List<TestParameter>>> parameterList = null;

	/**
	 * Value of the method ietations.
	 */
	private Hashtable<String, Integer> methodIteration = new Hashtable<String, Integer>();
	/**
	 * Runner instance for the Repast tests.
	 */
	private RepastRunner runner;

	/**
	 * Instrumented names to avoid frozen class exception.
	 */
	private Set<String> instrumentedNames = new HashSet<String>();

	/**
	 * Assertion error thrown by the active test method.
	 */
	private AssertionError thrownAssertionError = null;

	/**
	 * Thrown exception from the active test method.
	 */
	private Exception thrownException = null;

	/**
	 * Timeout simulation tick value for the tests.
	 */
	private double runUntilValue;

	/**
	 * Annotation of the current test method.
	 */
	private RatKitTest testAnnotation;

	/**
	 * initializes the tests runner class with given test class name.
	 * 
	 * @param klass
	 *            test class to run tests.
	 * @throws InitializationError
	 */
	public RatKitRunner(Class<?> klass) throws InitializationError {
		super(klass);
	}

	/**
	 * This method evaluates a list of test methods that is prepared by (@code
	 * {@link RatKitRunner#computeTestMethods()}). Given {@code FrameworkMethod}
	 * instance is evaluated by using {@code RunNotifier} instance.
	 * 
	 * @see org.junit.runners.BlockJUnit4ClassRunner#runChild(org.junit.runners.model.FrameworkMethod,
	 *      org.junit.runner.notification.RunNotifier)
	 */
	@Override
	protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
		// prepare description for the method..
		Description description = describeChild(method);
		if (method.getAnnotation(Ignore.class) != null) {
			// ignore test..
			notifier.fireTestIgnored(description);
		} else {
			if (method.getAnnotation(RatKitTest.class) != null) {
				if (description.getChildren().size() > 0) {
					// is parameter sweeping is defined execute children..
					for (Description desc : description.getChildren()) {
						// execute simulation test method...
						runRatKitChild((method), notifier, desc);
					}
				} else {
					// test main test method..
					runRatKitChild(method, notifier, description);
				}

			}
		}
	}

	/**
	 * Prepares the {@code Description} instance for the given test method
	 * {@code FrameworkMethod} instance.
	 * 
	 * @see org.junit.runners.BlockJUnit4ClassRunner#describeChild(org.junit.runners
	 *      .model.FrameworkMethod)
	 */
	@Override
	protected Description describeChild(FrameworkMethod method) {
		if (method.getAnnotation(RatKitTest.class) != null
				&& method.getAnnotation(Ignore.class) == null) {
			return describeRepeatTest(method);
		}
		return super.describeChild(method);
	}

	/**
	 * Prepares the test description using the {@code RatKitTest} annotation.
	 * 
	 * @param method
	 *            test method to create the test description.
	 * @return test description of the given test method.
	 */
	private Description describeRepeatTest(FrameworkMethod method) {
		// get the test method annotation...
		testAnnotation = method.getAnnotation(RatKitTest.class);
		// get run until value..
		runUntilValue = testAnnotation.runUntil();
		// get the scenario builder.
		this.scenarioBuilder = testAnnotation.scenarioBuilderClass();
		// create a new parameter list.
		List<TestParameter> paramList = new Vector<TestParameter>();
		// process parameters.
		RatKitParameter[] testParameters = testAnnotation.parameters();
		// if any test parameters exists process parameters.
		if (testParameters.length > 0) {
			for (RatKitParameter rastParameter : testParameters) {
				// get parameter values...
				String parameterName = rastParameter.parameterName();
				String parameterValue = rastParameter.parameterValue();
				String parameterType = rastParameter.parameterType();
				String from = rastParameter.from();
				String to = rastParameter.to();
				String step = rastParameter.step();
				Type type = rastParameter.type();
				// create TestParameter instance...
				TestParameter param = new TestParameter();
				param.setParameterName(parameterName);
				param.setParameterType(parameterType);
				if (type == Type.NUMBER) {
					param.setFrom(from);
					param.setTo(to);
					param.setStep(step);
				} else {
					param.setParameterValue(parameterValue);
				}
				param.setType(type);
				paramList.add(param);
			}
		}
		// add working directory parameter for test agent creation.
		TestParameter dirParam = new TestParameter();
		dirParam.setParameterName(RatKitConstants.WORKING_DIRECTORY);
		dirParam.setParameterType(ParameterTypes.STRING);
		dirParam.setParameterValue(getScenarioDir());
		dirParam.setType(Type.CONSTANT);
		paramList.add(dirParam);
		// set list.
		RatKitParameterSweeper producer = new RatKitParameterSweeper();
		List<List<TestParameter>> list = producer.sweepParameters(paramList);
		// prepare parameter sweeping parameter lists according to the
		// iteration.
		getParameterList().put(method.getName(), list);
		// define the parameter sweeping run count.
		methodIteration.put(method.getName(), 0);
		// define test method descriptions.
		if (list.size() > 1) {
			// if any parameter sweeping is defined create a test suite for the
			// test method..
			Description description = Description.createSuiteDescription(
					testName(method), method.getAnnotations());
			// process the parameter list to define test labels for each sweep
			// run.
			for (int i = 0; i < list.size(); i++) {
				// get the sweep run parameters...
				List<TestParameter> params = list.get(i);
				String testLabel = "";
				// create a test label with given sweep run parameters.
				for (TestParameter testParameter : params) {
					if (!testParameter.getParameterName().equals(
							RatKitConstants.WORKING_DIRECTORY)) {
						testLabel += testParameter.getParameterName() + ":"
								+ testParameter.getParameterValue();
					}
				}
				// create a new test description for the each sweep run..
				description.addChild(Description.createTestDescription(
						getTestClass().getJavaClass(), testName(method) + "["
								+ testLabel + "]"));
			}
			return description;
		} else {
			return super.describeChild(method);
		}
	}

	/**
	 * Executes the simulation tests that are annotated by @code
	 * {@link RatKitTest}. Given test method is executed accordingly to the
	 * given annotation parameters.
	 * 
	 * @param method
	 *            description of the test method.
	 * @param notifier
	 *            test notifier for the test execution.
	 * @param desc
	 *            description instance of the current test method.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void runRatKitChild(FrameworkMethod method, RunNotifier notifier,
			Description desc) {
		EachTestNotifier eachNotifier = new EachTestNotifier(notifier, desc);
		if (method.getAnnotation(Ignore.class) != null) {
			eachNotifier.fireTestIgnored();
			return;
		}
		// fire test started event...
		eachNotifier.fireTestStarted();
		try {
			// initialize runner.
			initialize(method);
			// register to the MessageCenter for Errors and Exceptions...
			registerAssertionListener(method);
			// schedule out test agent proper method...
			Context<Object> mainContext = runner.getContext();
			if (testAnnotation.shuffle()) {
				// get agent types...
				Iterable<Class> agentTypes = mainContext.getAgentTypes();
				// except test agent schedule all agent instances...
				for (Class agentClass : agentTypes) {
					// get all agent class instances in the context..
					if (!agentClass.getName().equals(
							getTestClass().getJavaClass().getName())) {
						IndexedIterable<Object> agents = mainContext
								.getObjects(agentClass);
						// schedule all agents...
						for (Object agent : agents) {
							runner.schedule(agent);
						}
					}
				}
			}
			// get test environment.
			Context<Object> testContext = mainContext
					.findContext(RatKitConstants.TEST_ENVIRONMENT);
			// get test agent instance for the test context.
			Object target = testContext.getObjects(
					getTestClass().getJavaClass()).get(0);
			// prepare test method schedule parameters.
			ScheduleParameters scheduleParameters = getScheduleParameters();
			// schedule test parameters with given parameters.
			runner.schedule(scheduleParameters, target, method.getName());
			// execute test scenario with run until value...
			executeTestScenario(runUntilValue);
			// check any assertion error occurred...
			if (thrownAssertionError != null) {
				throw thrownAssertionError;
			}
			// check any assertion error occurred...
			if (thrownException != null) {
				throw thrownException;
			}
		} catch (AssumptionViolatedException e) {
			// catch assumption exceptions
			eachNotifier.addFailedAssumption(e);
		} catch (Throwable e) {
			if (e.getClass().equals(testAnnotation.expected())) {
				// thrown exception is expected and current execution is true.
			} else {
				// notify for occurred errors.
				eachNotifier.addFailure(e);
			}
		} finally {
			// initialize runner
			testFinished();
			// fire test finished...
			eachNotifier.fireTestFinished();
		}
	}

	/**
	 * Returns the schedule parameters for the given annotation value.
	 * 
	 * @return {@code ScheduleParameters} instance for the given annotation
	 *         values.
	 */
	private ScheduleParameters getScheduleParameters() {
		ScheduleParameters scheduleParameters = null;
		// get test method interval value...
		double interval = testAnnotation.interval();
		if (interval == 0) {
			scheduleParameters = ScheduleParameters.createOneTime(
					testAnnotation.start(), testAnnotation.priority(),
					testAnnotation.duration());
		} else {
			scheduleParameters = ScheduleParameters.createRepeating(
					testAnnotation.start(), testAnnotation.interval(),
					testAnnotation.priority(), testAnnotation.duration());
		}

		return scheduleParameters;
	}

	/**
	 * initializes the the test runner for executing given method test.
	 * 
	 * @param method
	 *            method to run.
	 * @throws RatKitParameterException
	 */
	public void initialize(FrameworkMethod method)
			throws RatKitParameterException {
		// to avoid frozen class exception
		WatcheeInstrumentor.getInstrumented().addAll(instrumentedNames);
		// create required files for scenario
		initializeScenarioFiles(method);
		// initialize the runner.
		runner = new RepastRunner();
		try {
			// load the scenario
			File scenarioDir = new File(getScenarioDir());
			runner.loadScenario(scenarioDir);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// initialize the run
		runner.runInitialize();
	}

	/**
	 * Runs a given scenario for once until a certain amount of time unless it
	 * finished by itself.
	 * 
	 * @param runUntil
	 *            the time in which the simulation run is supposed to finish.
	 */
	public void executeTestScenario(double runUntil) {
		// loop until the last action is left
		while (runner.go() && runner.getActionCount() > 0) {
			if (runner.getModelActionCount() == 0) {
				runner.setFinishing(true);
			}
			// if this run exceeds the given time, the test fails.
			double currentTick = getCurrentTick();
			if ((currentTick >= runUntil) || (thrownAssertionError != null)
					|| (thrownException != null)) {
				runner.stop();
			} else {
				// execute all scheduled actions at next tick
				runner.step();
			}
		}
	}

	/**
	 * Fires the test finished event and completes the test method execution.
	 * And initializes test execution environment.
	 */
	public void testFinished() {
		// clean thrown exceptions..
		thrownException = null;
		thrownAssertionError = null;
		// to avoid frozen class exception
		instrumentedNames.addAll(WatcheeInstrumentor.getInstrumented());
		// execute any actions scheduled at run end
		if (runner != null) {
			runner.stop();
			// cleanUp run...
			runner.cleanUpRun();
			// clean up the batch after all runs complete
			runner.cleanUpBatch();
		}
		// remove all scenario files
		cleanUpScenarioFiles();
	}

	/**
	 * Initializes scenario files for the current execution.
	 * 
	 * @param method
	 *            test method to execute.
	 * @throws RatKitParameterException
	 */
	private void initializeScenarioFiles(FrameworkMethod method)
			throws RatKitParameterException {
		initializeDataLoaderFile();
		initializeContextXMLFile();
		initializeScenarioXMLFile();
		initializeParameterXMLFile(method);
		initializeUserPathXMLFile();
		initializeTestAgentFile();
	}

	/**
	 * Creates the parameters file for the current test execution.
	 * 
	 * @param method
	 * @throws RatKitParameterException
	 */
	private void initializeParameterXMLFile(FrameworkMethod method)
			throws RatKitParameterException {
		File parameterXMLFile = new File(getScenarioDir(),
				RatKitConstants.PARAMETERS_FILE);
		// write contents..
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					parameterXMLFile));
			bufferedWriter
					.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
			bufferedWriter.write("<parameters>\n");
			bufferedWriter
					.write("<parameter name=\"randomSeed\" displayName=\"Default Random Seed\" type=\"int\" defaultValue=\"__NULL__\" isReadOnly=\"false\" converter=\"repast.simphony.parameter.StringConverterFactory$IntConverter\"/>");
			List<List<TestParameter>> sweepingList = this.getParameterList()
					.get(method.getName());
			Integer val = methodIteration.get(method.getName());
			int index = val.intValue();
			List<TestParameter> list = sweepingList.get(index);
			methodIteration.put(method.getName(), new Integer(++index));
			for (TestParameter testParameter : list) {
				String parameterType = testParameter.getParameterType();
				String parameterName = testParameter.getParameterName();
				String converter = this.getParameterTypeConverters().get(
						parameterType);
				if (converter == null) {
					throw new RatKitParameterException(
							"Test parameter initialization is corrupted in the test method "
									+ method.getName() + " :" + parameterName
									+ " parameter type " + parameterType
									+ " is invalid");
				} else if (parameterName == null || parameterName.equals("")) {
					throw new RatKitParameterException(
							"Test parameter initialization is corrupted in the test method "
									+ method.getName() + " :"
									+ " parameter name is invalid");
				}

				bufferedWriter.write("<parameter name=\"" + parameterName
						+ "\" type=\"" + parameterType + "\" defaultValue=\""
						+ testParameter.getParameterValue()
						+ "\" isReadOnly=\"false\" converter=\"" + converter
						+ "\"/>");
			}
			// add last closing tag..
			bufferedWriter.write("</parameters>");
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Creates the user path xml file for the current test execution.
	 */
	private void initializeUserPathXMLFile() {
		String projectName = getProjectName(RatKitConstants.USER_PATH_FILE);
		File userPathXMLFile = new File(getScenarioDir(),
				RatKitConstants.USER_PATH_FILE);
		// create content of the user path file..
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					userPathXMLFile));
			String pkg = this.getScenarioPackage();
			StringTokenizer tokenizer = new StringTokenizer(pkg,
					RatKitConstants.PACKAGE_SEPERATOR);
			String binPath = getBinaryPath(tokenizer.countTokens());
			bufferedWriter
					.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
			String testAgentClassName = getScenarioPackage()
					+ RatKitConstants.PACKAGE_SEPERATOR
					+ getTestClass().getJavaClass().getSimpleName();
			bufferedWriter.write("<model name=\"" + projectName + "\">\n"
					+ "<classpath>\n" + "<agents path=\"" + binPath
					+ projectName + "/bin\" filter=\"" + testAgentClassName
					+ "\"/>\n </classpath>\n </model>");
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns the binary (./bin) folder of the project.
	 * 
	 * @param length
	 * @return
	 */
	private String getBinaryPath(int length) {
		int parentCount = 2 + length;
		String parentPath = "";
		for (int i = 0; i < parentCount; i++) {
			parentPath += "../";
		}
		return parentPath;
	}

	/**
	 * Gets the project name.
	 * 
	 * @param pathXML
	 *            path of the XML file to execute.
	 * @return name of the project.
	 */
	private String getProjectName(String pathXML) {
		File my = new File(pathXML);
		String path = my.getAbsolutePath();
		path = path.replace(pathXML, "");
		path = path.substring(0, path.lastIndexOf(File.separator));
		// delete dummy file..
		my.delete();
		return path.substring(path.lastIndexOf(File.separator) + 1);
	}

	/**
	 * Initializes the scenario xml file for the current test execution.
	 */
	public void initializeScenarioXMLFile() {
		// create the file.
		File scenarioXMLFile = new File(getScenarioDir(),
				RatKitConstants.SCENARIO_FILE);
		// write the file content.
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					scenarioXMLFile));
			bufferedWriter
					.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
			bufferedWriter.write("<Scenario>\n");
			bufferedWriter
					.write("<repast.simphony.dataLoader.engine.ClassNameDataLoaderAction ");
			bufferedWriter.write("file=\"");
			bufferedWriter.write(RatKitConstants.DATA_LOADER_FILE);
			bufferedWriter
					.write("\" context=\""
							+ getProjectName(RatKitConstants.SCENARIO_FILE)
							+ "\" />\n");
			bufferedWriter.write("</Scenario>");
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Initializes the context file for the current text execution.
	 */
	private void initializeContextXMLFile() {
		File contextXMLFile = new File(getScenarioDir(),
				RatKitConstants.CONTEXT_FILE);
		// write the file content.
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					contextXMLFile));
			bufferedWriter.write("<context id=\""
					+ getProjectName(RatKitConstants.CONTEXT_FILE) + "\">\n");
			bufferedWriter.write("</context>");
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Initiazlizes the data loader file for the current test execution.
	 */
	private void initializeDataLoaderFile() {
		File dataLoaderFile = new File(getScenarioDir(),
				RatKitConstants.DATA_LOADER_FILE);
		String dataLoader = "<string>"
				+ this.scenarioBuilder.getPackage().getName()
				+ RatKitConstants.PACKAGE_SEPERATOR
				+ this.scenarioBuilder.getSimpleName() + "</string>";
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					dataLoaderFile));
			bufferedWriter.write(dataLoader);
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Initializes the test agent file for the current test execution.
	 */
	private void initializeTestAgentFile() {
		File testAgentFile = new File(getScenarioDir(),
				RatKitConstants.TEST_AGENT_FILE);
		String dataLoader = "<string>" + getScenarioPackage()
				+ RatKitConstants.PACKAGE_SEPERATOR
				+ this.getTestClass().getJavaClass().getSimpleName()
				+ "</string>";
		try {
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
					testAgentFile));
			bufferedWriter.write(dataLoader);
			bufferedWriter.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method deletes the scenario files after test execution.
	 */
	private void cleanUpScenarioFiles() {
		File dataLoaderFile = new File(getScenarioDir(),
				RatKitConstants.DATA_LOADER_FILE);
		dataLoaderFile.delete();

		File contextXMLFile = new File(getScenarioDir(),
				RatKitConstants.CONTEXT_FILE);
		contextXMLFile.delete();

		File scenarioXMLFile = new File(getScenarioDir(),
				RatKitConstants.SCENARIO_FILE);
		scenarioXMLFile.delete();

		File parametersXMLFile = new File(getScenarioDir(),
				RatKitConstants.PARAMETERS_FILE);
		parametersXMLFile.delete();

		File userPathXMLFile = new File(getScenarioDir(),
				RatKitConstants.USER_PATH_FILE);
		userPathXMLFile.delete();

		File testAgentPathXMLFile = new File(getScenarioDir(), "test_agent.xml");
		testAgentPathXMLFile.delete();
	}

	/**
	 * @return the current scenario path for the current test class.
	 */
	protected String getScenarioPackage() {
		return this.getTestClass().getJavaClass().getPackage().getName();
	}

	/**
	 * @return the scenario directory.
	 */
	protected String getScenarioDir() {
		String scenarioDir = RatKitConstants.PACKAGE_SEPERATOR
				+ File.separator
				+ RatKitConstants.TEST_PACKAGE
				+ File.separator
				+ getScenarioPackage().replace(
						RatKitConstants.PACKAGE_SEPERATOR, File.separator)
				+ File.separator;
		return scenarioDir;
	}

	/**
	 * @return the scenario files directory that are created dynamically.
	 */
	protected String getScenarioDirectory() {
		String packageName = getScenarioPackage();
		String dataDir = RatKitConstants.PACKAGE_SEPERATOR
				+ File.separator
				+ RatKitConstants.TEST_PACKAGE
				+ File.separator
				+ packageName.replaceAll(RatKitConstants.PACKAGE_SEPERATOR,
						File.separator) + File.separator + "data"
				+ File.separator;
		return dataDir;
	}

	/**
	 * Registers the error listener of the runner ro the temod executer.
	 * 
	 * @param method
	 *            name of the test method to execute.
	 * @throws Exception
	 */
	public void registerAssertionListener(final FrameworkMethod method)
			throws Exception {
		MessageCenter.addMessageListener(new MessageEventListener() {
			@Override
			public void messageReceived(MessageEvent arg0) {

				if (thrownAssertionError == null || thrownException == null) {
					Throwable throwable = arg0.getThrowable();
					if (throwable instanceof AssertionError) {
						AssertionError err = (AssertionError) throwable;
						String localizedMessage = err.getLocalizedMessage();
						AssertionError assertionError = new AssertionError(
								localizedMessage + " at tick "
										+ getCurrentTick());
						assertionError.setStackTrace(throwable.getStackTrace());
						thrownAssertionError = assertionError;
					} else {
						Exception caughtEx = new Exception(
								"An exception occured during the execution of the \""
										+ method.getName()
										+ "\" method of the \""
										+ getTestClass().getJavaClass()
												.getName() + "\" class:"
										+ throwable.getClass().getName(),
								throwable);
						caughtEx.setStackTrace(throwable.getStackTrace());
						thrownException = caughtEx;
					}
				}
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.junit.runners.BlockJUnit4ClassRunner#computeTestMethods()
	 */
	@Override
	protected List<FrameworkMethod> computeTestMethods() {
		return getTestClass().getAnnotatedMethods(RatKitTest.class);
	}

	/**
	 * Adds to {@code errors} for each method annotated with {@code @Test}that
	 * is not a public, void instance method with no arguments.
	 */
	protected void validateTestMethods(List<Throwable> errors) {
		validatePublicVoidNoArgMethods(RatKitTest.class, false, errors);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.junit.runners.ParentRunner#validatePublicVoidNoArgMethods(java.lang
	 * .Class, boolean, java.util.List)
	 */
	@Override
	protected void validatePublicVoidNoArgMethods(
			Class<? extends Annotation> annotation, boolean isStatic,
			List<Throwable> errors) {
		super.validatePublicVoidNoArgMethods(RatKitTest.class, false, errors);
	}

	/**
	 * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation
	 * has the {@code expecting} attribute, return normally only if {@code next}
	 * throws an exception of the correct type, and throw an exception
	 * otherwise.
	 * 
	 * @deprecated Will be private soon: use Rules instead
	 */
	@Deprecated
	protected Statement possiblyExpectingExceptions(FrameworkMethod method,
			Object test, Statement next) {
		RatKitTest annotation = method.getAnnotation(RatKitTest.class);
		return expectsException(annotation) ? new ExpectException(next,
				getExpectedException(annotation)) : next;
	}

	/**
	 * Returns the expected exception for the given test method.
	 * 
	 * @param annotation
	 *            annotaion of the current test method.
	 * @return exception class.
	 */
	private Class<? extends Throwable> getExpectedException(
			RatKitTest annotation) {
		if (annotation == null || annotation.expected() == None.class)
			return null;
		else
			return annotation.expected();
	}

	/**
	 * Expected exception asserter method for the current test method execution.
	 * 
	 * @param annotation
	 *            annotation of the current test method.
	 * @return true if the expected execution exists.
	 */
	private boolean expectsException(RatKitTest annotation) {
		return getExpectedException(annotation) != null;
	}

	/**
	 * @return the current tick count of the current schedule.
	 */
	public double getCurrentTick() {
		return RunEnvironment.getInstance().getCurrentSchedule().getTickCount();
	}

	/**
	 * @return current parameter type conver lists.
	 */
	public Hashtable<String, String> getParameterTypeConverters() {
		if (this.parameterTypeConverters == null) {
			this.parameterTypeConverters = new Hashtable<String, String>();
			parameterTypeConverters.put(ParameterTypes.INT,
					RatKitConstants.INT_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.DOUBLE,
					RatKitConstants.DOUBLE_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.STRING,
					RatKitConstants.STRING_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.BOOLEAN,
					RatKitConstants.BOOLEAN_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.LONG,
					RatKitConstants.LONG_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.SHORT,
					RatKitConstants.SHORT_CONVERTER);
			parameterTypeConverters.put(ParameterTypes.FLOAT,
					RatKitConstants.FLOAT_CONVERTER);
		}
		return parameterTypeConverters;
	}

	/**
	 * @return the current parameter list of the current method execution.
	 */
	public Hashtable<String, List<List<TestParameter>>> getParameterList() {
		if (this.parameterList == null) {
			this.parameterList = new Hashtable<String, List<List<TestParameter>>>();
		}
		return parameterList;
	}
}